package jmine.selenium.services;

import java.util.List;

import jmine.tec.di.scope.TypedScope;
import jmine.tec.services.api.ActionsEnum;
import jmine.tec.services.api.Service;
import jmine.tec.services.api.ServiceExecutionException;
import jmine.tec.services.api.ServicesPage;
import jmine.tec.services.api.factory.ServiceCreationException;
import jmine.tec.services.api.factory.ServiceFactory;
import jmine.tec.services.execution.BaseServicesPageExecutor;

/**
 * Executor de serviços escritos em Selenese.
 * 
 * @author Rafael Volpato
 */
public class SeleniumServicesPageExecutor extends BaseServicesPageExecutor {

    private ServiceFactory serviceFactory;

    private boolean decorateServiceExecution = true;

    private String applicationServiceName;

    /**
     * Decora o metodo {@link TransactionAroundServiceExecutor#execute(List)} para executar o processo de login/logout antes/depois da
     * execução do cenario.
     * 
     * @param servicesPages servicesPages
     * @throws ServiceExecutionException se ocorrer algum erro
     */
    @Override
    public void execute(List<ServicesPage> servicesPages) throws ServiceExecutionException {
        TypedScope serviceProperties = this.findServiceProperties(servicesPages);
        if (serviceProperties == null) {
            return;
        }
        serviceProperties.put(SeleniumApplicationService.IDENTIFICADOR, SeleniumApplicationService.DEFAULT_SESSION);

        // Inicializar a sessao de teste de selenium. Se ocorrer algum erro durante a inicialização do teste deixar subir o erro.
        this.beforeExecute(serviceProperties);

        boolean executionOk = false;
        try {
            // Executar os serviços de teste
            super.execute(servicesPages);
            executionOk = true;
        } finally {
            // Marcar a sessao de teste de selenium como finalizada
            try {
                this.afterExecute(serviceProperties);
            } catch (ServiceExecutionException e) {
                if (executionOk) {
                    // Se o teste foi executado com sucesso mas ocorreu um erro no 'afterExecute', subir o erro gerado no afterExecute. Se
                    // ocorreu algum erro na execução do teste deixar subir o erro original (do teste).
                    throw e;
                }
            }
        }
    }

    /**
     * Busca um {@link TypedScope} dentro das {@link ServicesPage} informadas para execução.
     * 
     * @param servicesPages {@link ServicesPage}
     * @return {@link TypedScope}
     */
    private TypedScope findServiceProperties(List<ServicesPage> servicesPages) {
        TypedScope serviceProperties = null;
        if (!servicesPages.isEmpty()) {
            ServicesPage servicesPage = servicesPages.iterator().next();
            if (!servicesPage.getServices().isEmpty()) {
                serviceProperties = servicesPage.getServices().iterator().next().getServiceProperties();
                serviceProperties = serviceProperties.getParent() != null ? serviceProperties.getParent() : serviceProperties;
            }
        }
        return serviceProperties;
    }

    /**
     * Executa os passos necessarios antes da execução do cenario.
     * 
     * @param typedScope typedScope
     * @throws ServiceExecutionException se ocorrer algum erro
     */
    private void beforeExecute(TypedScope typedScope) throws ServiceExecutionException {
        // Executa primeiramente o servico de abertura/login
        if (this.applicationServiceName != null) {
            this.doExecuteService(ActionsEnum.PRE_REQ, this.applicationServiceName, typedScope);
        }

        this.doExecuteService(ActionsEnum.PROCESSAR, "Login", typedScope);
    }

    /**
     * Executa os passos necessarios apos a execução do cenario.
     * 
     * @param typedScope typedScope
     * @throws ServiceExecutionException se ocorrer algum erro
     */
    private void afterExecute(TypedScope typedScope) throws ServiceExecutionException {
        // Antes de finalizar a sessao de teste, finalizar a sessao da aplicação
        this.doExecuteService(ActionsEnum.PROCESSAR, "Logout", typedScope);
    }

    /**
     * Executa os serviços antes/depois da execução do cenario.
     * 
     * @param action action
     * @param serviceName serviceName
     * @param typedScope typedScope
     * @throws ServiceExecutionException se ocorrer algum erro durante a criação/execução do serviço
     */
    private void doExecuteService(ActionsEnum action, String serviceName, TypedScope typedScope) throws ServiceExecutionException {
        if (!this.decorateServiceExecution) {
            return;
        }

        this.getExecutionLogController().startTransaction();
        boolean rollback = true;
        try {
            Service service = this.serviceFactory.getService(action, serviceName, typedScope);
            service.execute();
            rollback = false;
        } catch (ServiceCreationException e) {
            throw new ServiceExecutionException(e);
        } finally {
            if (rollback) {
                this.getExecutionLogController().rollback();
            } else {
                this.getExecutionLogController().commit();
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected boolean needsNewTransaction(Service<?> service) {
        return true;
    }

    /**
     * @param serviceFactory the serviceFactory to set
     */
    public void setServiceFactory(ServiceFactory serviceFactory) {
        this.serviceFactory = serviceFactory;
    }

    /**
     * @param decorateServiceExecution the decorateServiceExecution to set
     */
    public void setDecorateServiceExecution(boolean decorateServiceExecution) {
        this.decorateServiceExecution = decorateServiceExecution;
    }

    /**
     * @return the decorateServiceExecution
     */
    public boolean isDecorateServiceExecution() {
        return this.decorateServiceExecution;
    }

    /**
     * @param applicationServiceName the applicationServiceName to set
     */
    public void setApplicationServiceName(String applicationServiceName) {
        this.applicationServiceName = applicationServiceName;
    }
}
